/**
 * @Description: 使用帮助函数
 * @author 刘贵
 * @date 2020/11/16
 */

const exports = {
  /**
   * @description 字符串解析成json
   * @param {String} jsonString
   * @param {*} errorReturnData 解析出错时返回的值
   * @returns {Object}
   */
  parseJson (jsonString, errorReturnData = {}) {
    try {
      return JSON.parse(jsonString)
    } catch (e) {
      return errorReturnData
    }
  },

  /**
   * @description json字符串化
   * @param {String} jsonData
   * @param {*} errorReturnData 解析出错时返回的值
   * @returns {String}
   */
  stringifyJson (jsonData, errorReturnData = '{}') {
    try {
      return JSON.stringify(jsonData)
    } catch (e) {
      return errorReturnData
    }
  },

  /**
   * @description 字符串转对象
   * @param {String} sourceString 要转换的字符串对象
   * @param {String} valueIdentifier 键和值得分割点
   * @param {String} varIdentifier 一个键和一个值组成一条完整属性，它作为多个完整属性的分割点
   */
  stringParseObject: function (sourceString, valueIdentifier = '=', varIdentifier = '&') {
    const store = {}
    let string = ''
    let ary = []
    let aryCopy = []

    if (sourceString) {
      if (typeof (sourceString) === 'string') {
        string = sourceString
      } else if (this.isPlainObject(sourceString)) {
        return sourceString
      } else {
        return store
      }

      ary = string.split(varIdentifier)

      for (let i = 0; i < ary.length; i++) {
        aryCopy = ary[i].split(valueIdentifier)
        if (aryCopy.length === 2) {
          store[aryCopy[0]] = aryCopy[1]
        }
      }
      return store
    }
    return store
  },

  /**
   * @description 对象转字符串
   * @param {Object} sourceObject 要转换的对象串对象
   * @param {String} valueIdentifier 键和值得分割点
   * @param {String} varIdentifier 一个键和一个值组成一条完整属性，它作为多个完整属性的分割点
   * @param {String} transcode 是否转码 url链接时可能需要
   */
  objectParseString: function (sourceObject, valueIdentifier = '=', varIdentifier = '&', transcode = '') {
    let string = ''
    if (sourceObject) {
      for (let n in sourceObject) {
        let strVal
        strVal = sourceObject[n]
        if (transcode) {
          strVal = transcode === 'encode' ? encodeURIComponent(strVal) : transcode === 'decode' ? decodeURIComponent(strVal) : strVal
        }
        strVal = n + valueIdentifier + strVal
        string += string ? varIdentifier + strVal : strVal
      }
    }
    return string
  },

  /**
   * @description  往一个类似链接的字符串对象中添加参数
   * @param {String} href 原始字符串对象
   * @param {Object} params 要添加的参数
   * @param {String} valueIdentifier =
   * @param {String} varIdentifier &
   * @param {Boolean} encode
   */
  addParams: function (href, params, valueIdentifier = '=', varIdentifier = '&', encode = true) {
    let objectParams = params || ''
    let stringParams
    let has
    let newHref = typeof (href) === 'string' ? href : window.location.href + ''

    if (!this.isPlainObject(objectParams)) {
      objectParams = this.stringParseObject(objectParams, valueIdentifier, varIdentifier)
    }

    has = newHref.split('#')
    newHref = has.splice(0, 1).join('')
    if (/\?(.+=|.+|$)/.test(newHref)) {
      newHref += newHref.charAt(newHref.length - 1) === '&' ? '' : '&'
      for (const n in objectParams) {
        let reg = '(\\?|&)(' + this.escapeRegExp(n) + '=.*?&)'
        reg = new RegExp(reg, 'g')
        newHref = newHref.replace(reg, function (a, b) {
          return b
        })
      }
      stringParams = this.objectParseString(objectParams, '=', '&', encode === false ? '' : 'encode') || ''
      newHref += stringParams
    } else {
      stringParams = this.objectParseString(objectParams, '=', '&', encode === false ? '' : 'encode') || ''
      newHref += '?' + stringParams
    }

    newHref += (has[0] ? '#' + has.join('#') : '')
    return newHref
  },

  each: function (list, callBack) {
    let len, res
    if (list) {
      if (list.length && this.isArray(list)) {
        len = list.length
        for (let i = 0; i < len; i++) {
          res = callBack.call(list, i, list[i])
          if (res === false) {
            break
          } else if (res === true) {
            continue
          }
        }
      } else if (this.isPlainObject(list)) {
        for (let n in list) {
          res = callBack.call(list, n, list[n])
          if (res === false) {
            break
          } else if (res === true) {
            continue
          }
        }
      }
    }
  },

  /**
   * @description 从一个对象中取一组key值，组合返回一个新对象
   * @param {Array} keys 多个key[String | function] 如果key为function，要求返回值为数组，如：['keyName', 'keyValue]
   * @param {Array|Object} sourceData 源数据
   * @param {*} defaultData // 如果sourceData内没有对应的key，赋予的默认值（undefined）
   * @param {Boolean} allowAlias // 允许使用别名 在一些高频率场景中如遍历中，如果遇到性能问题可关闭别名使用可能会有所帮助。注：不管你是否使用别名默认是开启的，别名使用用 as 指向（id as dataId）
   * @param {Boolean} strictHasEmpty // 使defaultData生效的空值范围，为true时空值范围包含 null,undefined,''，否则只包含undefined
   * @returns {Object}
   */
  elements: function (keys, sourceData, defaultData = undefined, { allowAlias = true, strictHasEmpty = false } = {}) {
    const data = {}
    if (!sourceData) {
      sourceData = {}
    }

    function isEmpty (value) {
      if (value) {
        return false
      } else if (strictHasEmpty === true) {
        return value === undefined || value === '' || value === null
      } else {
        return value === undefined
      }
    }

    keys.forEach(key => {
      let value
      let getValueKey
      let setValueKey

      if (typeof key === 'function') {
        const res = key(sourceData)
        data[res[0]] = res[1] || defaultData
        return
      } else if (this.isArray(key)) {
        data[key[0]] = key[1] || defaultData
      } else if (allowAlias === true) {
        const valueKeys = key.split(/\s+as\s+/ig)
        getValueKey = valueKeys[0]
        setValueKey = valueKeys[1] || getValueKey
      } else {
        getValueKey = key
        setValueKey = key
      }

      if (getValueKey) {
        value = this.get(sourceData, getValueKey)
        data[setValueKey] = !isEmpty(value) ? value : defaultData
      }
    })
    return data
  },

  /**
   * @description 校验数据的完整性，不允许存在ruleValue内的值
   * @param {Object|Array} data 需要校验的数据
   * @param {Array} verificationList 需要校验的字段（key)
   * @param {Array|Function} ruleValue 校验不允许存在的值 函数返回值参考[].includes返回值
   * @param {Boolean} isVerificationAll 是否校验完所有数据，为true时会校验完所有数据，false时检验到有ruleValue内的值会就此中断，不再继续
   * @returns {Object}
   */
  integrityVerification (data, verificationList = [], ruleValue = [undefined, '', null], isVerificationAll = false) {
    let needVerificationData = {}
    const ruleIsFunction = this.isFunction(ruleValue)
    const result = {
      isComplete: true,
      abnormalData: []
    }
    if (data && ((ruleValue && ruleValue.length) || ruleIsFunction)) {
      if (verificationList && verificationList.length) {
        verificationList.forEach(key => {
          needVerificationData[key] = data[key]
        })
      } else {
        needVerificationData = data
      }
      this.each(data, function (index, value) {
        const res = ruleIsFunction ? ruleValue(value) : ruleValue.includes(value)
        if (res === true) {
          result.isComplete = false
          result.abnormalData.push({
            value,
            key: index,
            message: index + ' is ' + (value === '' ? 'empty' : value)
          })
          if (isVerificationAll === false) {
            return false
          }
        }
      })
    }
    return result
  },

  integrityVerificationSingle (val, ruleValue = undefined) {
    return this.integrityVerification([val], [0], ruleValue)
  },

  date: {
    UNITS: {
      '年': 31557600000,
      '个月': 2629800000,
      '天': 86400000,
      '小时': 3600000,
      '分钟': 60000,
      '秒': 1000
    },
    humanizeToBefore: function (milliseconds) {
      let humanize = ''
      for (let key in this.UNITS) {
        if (milliseconds >= this.UNITS[key]) {
          humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前'
          break
        }
      }
      return humanize || '刚刚'
    },

    /**
     * 轻量化时间显示
     * A1:如新闻的 5分钟前,1天前 2019-09-20 :15: 12 等等
     * A2:聊天记录显示 2019年2月30日, 11月01日, 13:45 等等
     * @param {*} dateStr 能表示时间尺度的数据 yyyy-mm-dd HH:ii:ss || 1400xxxxxxxxxxx || Date || {year: 2019, month: 08, ...} , 如果为空则取当前时间
     * @param {Boolean|Number} day 有不同的取值 {
     *    undefined:时间差小于一天时返回 XX前 格式, 大于一天后 根据 light 返回不同值;
     *    false: 强制返回 xx前 格式;
     *    0: 跳过 xx前格式检测;
     *    >= 1: 时间差小于 day 天时返回 XX前 格式,否则根据 light 取值
     *  }
     * @param {Boolean} light true: 返回A2 || false: 返回A1 此时受day限制, 时间差小于day天时返回 XX前 格式,否则返回 yyyy-mm-dd HH:ii
     * @param {Number} detail 1: 会强制返回时和分，但如果不是同年则只显示年份/月/日；2: 会强制返回时和分，不管是不是同一年份
     * @param {Object} formatData 格式化日期字符格式, {Y: '-', m:'-', d: '-', H:':', i: ':', s: ':', W: '星期'} 此类格式
     * @param {Boolean} isBrief 是否是返回简洁的日期格式 即 2019-5-16 3:5:16, 否则补全为2位数
     * @param {String} suffixText 后缀显示文字
     */
    humanize: function (dateStr, day = 1, { light = false, detail = 0, formatData = {}, isBrief = false, suffixText = '' } = {}) {
      let countDay = day === undefined ? (+day || 1) : day
      let date = this.parse(dateStr)
      let nowDate = this.parse()
      const diff = nowDate.getTime() - date.getTime()
      const formatData_ = formatData || {}
      const suffix = suffixText || ''
      const dataLength = isBrief ? 1 : 2

      const formatStr = {
        Y: formatData_.Y || '/',
        m: formatData_.m || '/',
        d: formatData_.d || '',

        H: formatData_.H || ':',
        i: formatData_.i || '',
        s: formatData_.s || '',

        W: formatData_.W || '星期',

        spa: formatData_.spa || ' '
      }

      countDay = countDay > 0 && Number.isInteger(countDay) ? countDay + 1 : countDay

      if (countDay === false || (diff <= this.UNITS['天'] * countDay && diff >= 0)) {
        return this.humanizeToBefore(diff)
      }

      if (light) {
        let detailData = ''
        date = this.formatObject(date)
        nowDate = this.formatObject(nowDate)

        if (date.year !== nowDate.year) {
          if (detail === 2) {
            detailData = formatStr.d + formatStr.spa + 'H' + formatStr.H + 'i' + formatStr.i
          }
          return this.format('Y' + formatStr.Y + 'm' + formatStr.m + 'd' + detailData + suffix, date, dataLength)
        } else if (date.month !== nowDate.month || (nowDate.day - date.day >= 7)) {
          if (detail === 1 || detail === 2) {
            detailData = formatStr.spa + 'H' + formatStr.H + 'i' + formatStr.i
          }
          return this.format('m' + formatStr.m + 'd' + formatStr.d + detailData + suffix, date, dataLength)
        } else if (date.day !== nowDate.day) {
          if (nowDate.day - date.day === 1 || nowDate.day - date.day === -1) {
            let dayStr = '昨天'
            if (detail === 1 || detail === 2) {
              detailData = ' H' + formatStr.H + 'i' + formatStr.i
            }
            if (nowDate.day - date.day === -1) {
              dayStr = '明天'
            }
            return dayStr + (suffix || detailData ? this.format(detailData + suffix, date, dataLength) : '')
          } else if (nowDate.day - date.day < 7) {
            if (detail === 1 || detail === 2) {
              detailData = formatStr.spa + 'H' + formatStr.H + 'i' + formatStr.i
            }
            return this.format(formatStr.W + 'W' + detailData + suffix, date, dataLength)
          }
        } else {
          if ((detail === 1 || detail === 2) && date.day === nowDate.day) {
            detailData = '今天 '
          }
          return detailData + this.format('H' + formatStr.H + 'i' + formatStr.i, date, dataLength)
        }
      }

      return this.format('Y' + formatStr.Y + 'm' + formatStr.m + 'd' + formatStr.d + formatStr.spa + 'H' + formatStr.H + 'i' + formatStr.i, date, dataLength)
    },

    /**
     * @description 将"yyyy-mm-dd HH:MM:ss"格式的字符串，转化为一个Date对象
     * @param { String|Date|Object|Array } data 字符串时间对象
     * @returns {Date}
     */
    parse: function (data = '') {
      let dates
      if (!data) {
        dates = new Date()
      } else if (toString.call(data) === '[object Date]') {
        dates = data
      } else if (toString.call(data) === '[object Object]') {
        dates = new Date(data.year, data.month - 1, data.day, data.hour, data.minute, data.second)
      } else if (/^[0-9]{2,}$/.test(data)) {
        dates = new Date(parseInt(data))
      } else {
        dates = data.split(/[^0-9]/)
        dates = new Date(dates[0], (dates[1] - 1) || 0, dates[2] || 1, dates[3] || 0, dates[4] || 0, dates[5] || 0)
      }
      return dates
    },

    /**
     * @description 时间格式化成字符串 yyyy-mm-dd HH:ii:ss 样式
     * @description 使用 Y-m-d H:i:s 等等
     * @param { String } format 格式化字符串 YYYY-MM-DD
     * @param { Date | String } date 一个可转化为date对象的目标
     * @param { Number } suffixCount 时分秒日期等等 补全2位 default = 2
     * @returns {*}
     */
    format: function (format, date = null, suffixCount = 2) {
      let jsDate = this.parse(date)

      suffixCount = suffixCount || 2
      const pad = function (n, c) {
        if ((n = n + '').length < c) {
          return new Array(++c - n.length).join('0') + n
        } else {
          return n
        }
      }

      const txtWeekdaysCh = ['日', '一', '二', '三', '四', '五', '六']
      const txtWeekDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
      const txtOrdin = { 1: 'st', 2: 'nd', 3: 'rd', 21: 'st', 22: 'nd', 23: 'rd', 31: 'st' }
      const txtMonths = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
      const f = {
        // Day
        d: function () {
          return pad(f.j(), suffixCount)
        },
        D: function () {
          return f.l().substr(0, 3)
        },
        j: function () {
          return jsDate.getDate()
        },
        l: function () {
          return txtWeekDays[f.w()]
        },
        W: function () {
          return txtWeekdaysCh[f.w()]
        },
        N: function () {
          return f.w() + 1
        },
        S: function () {
          return txtOrdin[f.j()] ? txtOrdin[f.j()] : 'th'
        },
        w: function () {
          return jsDate.getDay()
        },
        z: function () {
          return (jsDate - new Date(jsDate.getFullYear() + '/1/1')) / 864e5 >> 0
        },

        // Week
        /* W: function(){
          var a = f.z(), b = 364 + f.L() - a;
          var nd2, nd = (new Date(jsDate.getFullYear() + "/1/1").getDay() || 7) - 1;
          if(b <= 2 && ((jsDate.getDay() || 7) - 1) <= 2 - b){
            return 1;
          } else{
            if(a <= 2 && nd >= 4 && a >= (6 - nd)){
              nd2 = new Date(jsDate.getFullYear() - 1 + "/12/31");
              return date("W", Math.round(nd2.getTime()/1000));
            } else{
              return (1 + (nd <= 3 ? ((a + nd) / 7) : (a - (7 - nd)) / 7) >> 0);
            }
          }
        }, */

        // Month
        F: function () {
          return txtMonths[f.n()]
        },
        m: function () {
          return pad(f.n(), suffixCount)
        },
        M: function () {
          return f.F().substr(0, 3)
        },
        n: function () {
          return jsDate.getMonth() + 1
        },
        t: function () {
          let n
          if ((n = jsDate.getMonth() + 1) === 2) {
            return 28 + f.L()
          } else {
            if ((n & 1 && n < 8) || (!(n & 1) && n > 7)) {
              return 31
            } else {
              return 30
            }
          }
        },

        // Year
        L: function () {
          const y = f.Y()
          return (!(y & 3) && (y % 1e2 || !(y % 4e2))) ? 1 : 0
        },
        // o not supported yet
        Y: function () {
          return jsDate.getFullYear()
        },
        y: function () {
          return (jsDate.getFullYear() + '').slice(2)
        },

        // Time
        a: function () {
          return jsDate.getHours() > 11 ? 'pm' : 'am'
        },
        A: function () {
          return f.a().toUpperCase()
        },
        B: function () {
          // peter paul koch:
          const off = (jsDate.getTimezoneOffset() + 60) * 60
          const theSeconds = (jsDate.getHours() * 3600) + (jsDate.getMinutes() * 60) + jsDate.getSeconds() + off
          let beat = Math.floor(theSeconds / 86.4)
          if (beat > 1000) beat -= 1000
          if (beat < 0) beat += 1000
          if ((String(beat)).length === 1) beat = '00' + beat
          if ((String(beat)).length === 2) beat = '0' + beat
          return beat
        },
        g: function () {
          return jsDate.getHours() % 12 || 12
        },
        G: function () {
          return jsDate.getHours()
        },
        h: function () {
          return pad(f.g(), suffixCount)
        },
        H: function () {
          return pad(jsDate.getHours(), suffixCount)
        },
        i: function () {
          return pad(jsDate.getMinutes(), suffixCount)
        },
        s: function () {
          return pad(jsDate.getSeconds(), suffixCount)
        },
        // u not supported yet

        // Timezone
        // e not supported yet
        // I not supported yet
        O: function () {
          let t = pad(Math.abs(jsDate.getTimezoneOffset() / 60 * 100), 4)
          if (jsDate.getTimezoneOffset() > 0) {
            t = '-' + t
          } else {
            t = '+' + t
          }
          return t
        },
        P: function () {
          const O = f.O()
          return (O.substr(0, 3) + ':' + O.substr(3, 2))
        },
        // T not supported yet
        // Z not supported yet

        // Full Date/Time
        c: function () {
          return f.Y() + '-' + f.m() + '-' + f.d() + 'T' + f.h() + ':' + f.i() + ':' + f.s() + f.P()
        },
        // r not supported yet
        U: function () {
          return Math.round(jsDate.getTime() / 1000)
        }
      }

      return format.replace(/[\\]?([a-zA-Z])/g, function (t, s) {
        let ret
        if (t !== s) {
          // escaped
          ret = s
        } else if (f[s]) {
          // a date function exists
          ret = f[s]()
        } else {
          // nothing special
          ret = s
        }
        return ret
      })
    },

    /**
     * @description 日期对象转化成对象
     * @param date_ 一个可转化为date对象的目标
     * @returns {{month: number, hour: number, year: number, day: number, minute: number, second: number}}
     */
    formatObject: function (date_) {
      const date = this.parse(date_)
      const year = date.getFullYear()
      const month = date.getMonth() + 1
      const day = date.getDate()

      const hour = date.getHours()
      const minute = date.getMinutes()
      const second = date.getSeconds()

      return {
        year: year,
        month: month,
        day: day,
        hour: hour,
        minute: minute,
        second: second
      }
    }
  },

  /**
   * @description 打开新页面
   * @param {String} url 地址
   */
  open (url) {
    const a = document.createElement('a')
    a.setAttribute('href', url)
    a.setAttribute('target', '_blank')
    a.setAttribute('id', 'ec-link-temp')
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(document.getElementById('ec-link-temp'))
  },

  /**
   * @description 更新标题
   * @param {String}  titleText 要更新的标题
   */
  title (titleText) {
    window.document.title = titleText || window.servers.title || ''
  }
}

module.exports = exports
